package cn.bingoo.handy;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.border.EmptyBorder;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;

import cn.bingoo.lang.Ofs;
import cn.bingoo.lang.Tuple2;
import cn.bingoo.swing.utils.Colors;

public class XmlFrame extends JFrame {
    private static final long serialVersionUID = -5458916873755699323L;
    private JPanel contentPane;

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    XmlFrame frame = new XmlFrame();
                    frame.setVisible(true);
                }
                catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Create the frame.
     */
    public XmlFrame() {
        setTitle("XML and JSON");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100, 100, 712, 487);
        contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        setContentPane(contentPane);
        GridBagLayout gbl_contentPane = new GridBagLayout();
        gbl_contentPane.columnWidths = new int[] { 0, 0 };
        gbl_contentPane.rowHeights = new int[] { 0, 0, 0 };
        gbl_contentPane.columnWeights = new double[] { 1.0, Double.MIN_VALUE };
        gbl_contentPane.rowWeights = new double[] { 1.0, 0.0, Double.MIN_VALUE };
        contentPane.setLayout(gbl_contentPane);

        JScrollPane scrollPane = new JScrollPane();
        GridBagConstraints gbc_scrollPane = new GridBagConstraints();
        gbc_scrollPane.fill = GridBagConstraints.BOTH;
        gbc_scrollPane.insets = new Insets(0, 0, 5, 0);
        gbc_scrollPane.gridx = 0;
        gbc_scrollPane.gridy = 0;
        contentPane.add(scrollPane, gbc_scrollPane);

        JPanel panel_2 = new JPanel();
        scrollPane.setViewportView(panel_2);
        panel_2.setLayout(new BorderLayout(0, 0));

        final JTextPane textPane = new JTextPane();
        panel_2.add(textPane, BorderLayout.CENTER);

        JPanel panel = new JPanel();
        GridBagConstraints gbc_panel = new GridBagConstraints();
        gbc_panel.fill = GridBagConstraints.BOTH;
        gbc_panel.gridx = 0;
        gbc_panel.gridy = 1;
        contentPane.add(panel, gbc_panel);
        GridBagLayout gbl_panel = new GridBagLayout();
        gbl_panel.columnWidths = new int[] { 0, 0, 0, 0 };
        gbl_panel.rowHeights = new int[] { 0, 0 };
        gbl_panel.columnWeights = new double[] { 0.0, 0.0, 0.0, Double.MIN_VALUE };
        gbl_panel.rowWeights = new double[] { 0.0, Double.MIN_VALUE };
        panel.setLayout(gbl_panel);

        JButton btnPrettyFormatXml = new JButton("Pretty Format XML");
        GridBagConstraints gbc_btnPrettyFormatXml = new GridBagConstraints();
        gbc_btnPrettyFormatXml.insets = new Insets(0, 0, 0, 5);
        gbc_btnPrettyFormatXml.gridx = 0;
        gbc_btnPrettyFormatXml.gridy = 0;
        panel.add(btnPrettyFormatXml, gbc_btnPrettyFormatXml);

        JButton btnCompactFormatXml = new JButton("Compact Format XML");
        btnCompactFormatXml.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String xml = findXml(textPane.getText());
                if (xml == null) {
                    return;
                }

                String compactFormat = XmlFormatter.compactFormat(xml);
                textPane.setText(compactFormat);
                colorXML(textPane);
            }
        });
        GridBagConstraints gbc_btnCompactFormatXml = new GridBagConstraints();
        gbc_btnCompactFormatXml.insets = new Insets(0, 0, 0, 5);
        gbc_btnCompactFormatXml.gridx = 1;
        gbc_btnCompactFormatXml.gridy = 0;
        panel.add(btnCompactFormatXml, gbc_btnCompactFormatXml);

        JButton btnRemoveSvccont = new JButton("Remove SvcCont");
        btnRemoveSvccont.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String xml = findXml(textPane.getText());
                if (xml == null) {
                    return;
                }

                xml = xml.replaceAll("<\\?.+?\\?>", "");
                xml = xml.replaceFirst("<SvcCont><\\!\\[CDATA\\[", "<SvcCont>");
                xml = xml.replaceFirst("\\]\\]></SvcCont>", "</SvcCont>");
                String prettyPrint = XmlFormatter.prettyPrint(xml);
                textPane.setText(prettyPrint);

                colorXML(textPane);

            }
        });
        GridBagConstraints gbc_btnRemoveSvccont = new GridBagConstraints();
        gbc_btnRemoveSvccont.gridx = 2;
        gbc_btnRemoveSvccont.gridy = 0;
        panel.add(btnRemoveSvccont, gbc_btnRemoveSvccont);
        btnPrettyFormatXml.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String xml = findXml(textPane.getText());
                if (xml == null) {
                    return;
                }

                String prettyPrint = XmlFormatter.prettyPrint(xml);
                textPane.setText(prettyPrint);
                colorXML(textPane);
            }
        });

    }

    private void colorXML(JTextPane textPane) {
        // processing instruction
        Pattern piPattern = Pattern.compile("<\\?.*\\?>", Pattern.DOTALL);
        Pattern commentPattern = Pattern.compile("<!--.*?-->", Pattern.DOTALL);
        Pattern tagPattern = Pattern.compile("(</?\\w+)[^<>]*>", Pattern.DOTALL);
        Pattern attrPattern = Pattern.compile("(\\w+)\\s*=\\s*(\"[^<>\"]*\")", Pattern.DOTALL);

        StyledDocument doc = textPane.getStyledDocument();

        // Makes text red
        Style style = textPane.addStyle("BLUE", null);
        StyleConstants.setForeground(style, Color.BLUE);
        style = textPane.addStyle("DARK_GREEN", null);
        StyleConstants.setForeground(style, Colors.DARK_GREEN);
        style = textPane.addStyle("RED", null);
        StyleConstants.setForeground(style, Color.RED);
        style = textPane.addStyle("DARK_BLUE", null);
        StyleConstants.setForeground(style, Colors.DARK_BLUE);
        style = textPane.addStyle("BLACK", null);
        StyleConstants.setForeground(style, Color.BLACK);

        String text = textPane.getText();
        doc.setCharacterAttributes(0, text.length(), textPane.getStyle("BLACK"), true);
        Matcher matcher = piPattern.matcher(text);
        ArrayList<Tuple2<Integer, Integer>> ranges = new ArrayList<Tuple2<Integer, Integer>>();

        while (matcher.find()) {
            // Set text in the range [5, 7) red
            Tuple2<Integer, Integer> r = Ofs.of(matcher.start(), matcher.group().length());
            doc.setCharacterAttributes(r._1, r._2, textPane.getStyle("BLUE"), true);
            ranges.add(r);
        }

        matcher = commentPattern.matcher(text);
        while (matcher.find()) {
            Tuple2<Integer, Integer> r1 = Ofs.of(matcher.start(), matcher.group().length());
            if (exitsInterception(ranges, r1)) {
                continue;
            }
            doc.setCharacterAttributes(r1._1, r1._2, textPane.getStyle("DARK_GREEN"), true);
            ranges.add(r1);
        }

        matcher = tagPattern.matcher(text);
        while (matcher.find()) {
            Tuple2<Integer, Integer> r1 = Ofs.of(matcher.start(), matcher.group().length());
            if (exitsInterception(ranges, r1)) {
                continue;
            }

            doc.setCharacterAttributes(matcher.start(1), matcher.group(1).length(), textPane.getStyle("BLUE"), true);
            int closingLength = matcher.group().endsWith("/>") ? 2 : 1;
            doc.setCharacterAttributes(matcher.end() - closingLength, closingLength, textPane.getStyle("BLUE"), true);

            Matcher attrMatcher = attrPattern.matcher(matcher.group());
            while (attrMatcher.find()) {
                doc.setCharacterAttributes(matcher.start() + attrMatcher.start(1),
                        attrMatcher.group(1).length(), textPane.getStyle("RED"), true);
                doc.setCharacterAttributes(matcher.start() + attrMatcher.start(2),
                        attrMatcher.group(2).length(), textPane.getStyle("DARK_BLUE"), true);
            }
        }
    }

    private boolean exitsInterception(ArrayList<Tuple2<Integer, Integer>> ranges, Tuple2<Integer, Integer> r1) {
        for (Tuple2<Integer, Integer> r2 : ranges) {
            // [from1, to1) [from2, to2) interception detect
            if (r1._1 + r1._2 <= r2._1 || r2._1 + r2._2 <= r1._1) {
                continue;
            }
            return true;
        }

        return false;
    }

    private String findXml(final String xml) {
        int startPos = xml.indexOf('<');
        int endPos = xml.lastIndexOf('>');
        if (startPos < 0 || endPos <= startPos) {
            return null;
        }
        return xml.substring(startPos, endPos + 1);
    }
}
